Hasta ahora, cuando querías filtrar o transformar los elementos de un generador o cualquier otro iterador, tenías que convertirlo primero a array. El patrón era algo así:
const resultado = Array.from(miIterador)
.filter(x => x % 2 === 0)
.map(x => x * 2)
.slice(0, 5);
El problema es evidente: si el iterador produce un millón de elementos, primero creas un array con el millón, luego otro con los pares, luego otro con los multiplicados. Todo eso en memoria, aunque al final solo necesites cinco valores.
Los iterator helpers resuelven exactamente eso. Con ES2025 puedes llamar a .filter(), .map(), .take() y otros métodos directamente sobre cualquier iterador, sin crear arrays intermedios y de forma lazy: los elementos no se procesan hasta que realmente se consumen.
Ya son Baseline 2025, así que están disponibles sin polyfill en Chrome 122+, Firefox 131+, Safari 18+ y Node.js 22+. No hace falta instalar nada en proyectos modernos.
Los métodos disponibles
La propuesta añade estos helpers al prototipo de los iteradores:
- .map(fn): transforma cada elemento con la función que le pases.
- .filter(fn): descarta los elementos para los que fn devuelve falso.
- .take(n): toma solo los primeros N elementos y para de iterar ahí mismo. No procesa el resto.
- .drop(n): salta los primeros N elementos y sigue desde el siguiente.
- .flatMap(fn): como map, pero aplana un nivel si fn devuelve un iterable.
- .forEach(fn): consume el iterador aplicando fn a cada elemento, sin devolver nada.
- .reduce(fn, initial): acumula un resultado recorriendo todos los elementos.
- .toArray(): materializa el iterador en un array cuando sí lo necesitas.
- .some(fn) / .every(fn) / .find(fn): cortocircuitan en cuanto pueden, igual que sus equivalentes en arrays.
El comportamiento lazy es la clave. Métodos como .map() o .filter() devuelven un nuevo iterador sin ejecutar nada todavía. La ejecución real ocurre solo cuando consumes el resultado, por ejemplo con .toArray() o en un bucle for...of.
Ejemplo práctico: procesar un generador grande
Imagina un generador que produce números indefinidamente:
function* numeros() {
let n = 0;
while (true) yield n++;
}
Con los iterator helpers, filtrar los pares, multiplicarlos por 2 y quedarte con los cinco primeros es así:
const resultado = numeros()
.filter(x => x % 2 === 0)
.map(x => x * 2)
.take(5)
.toArray();
console.log(resultado); // [0, 2, 4, 6, 8]
Solo se han procesado los elementos necesarios para obtener cinco pares. En cuanto .take(5) tiene suficiente, para. El generador ni siquiera llega a producir el sexto número válido.
Compara eso con la versión basada en arrays:
// ? Versión eager: tres arrays temporales en memoria
const todos = [];
for (let i = 0; i < 1_000_000; i++) todos.push(i);
const resultado = todos
.filter(x => x % 2 === 0) // array de 500.000 elementos
.map(x => x * 2) // otro array de 500.000 elementos
.slice(0, 5); // por fin, solo 5
Con un generador infinito, la versión de array directamente no funciona. Con los iterator helpers, funciona sin problema.
Iterator.from(): cualquier iterable con helpers
Los helpers solo están disponibles en iteradores nativos como generadores. Para usar estos métodos con un Set, un Map o una NodeList, tienes Iterator.from():
const numeros = new Set([1, 2, 3, 4, 5, 6, 7, 8]);
const resultado = Iterator.from(numeros)
.filter(x => x % 2 === 0)
.map(x => x ** 2)
.toArray();
console.log(resultado); // [4, 16, 36, 64]
Sin Iterator.from(), tendrías que convertir el Set a array primero. Con esto, cualquier objeto iterable tiene acceso a todos los helpers de forma inmediata.
Funciona igual con entradas de un Map:
const precios = new Map([
['manzana', 1.2],
['pera', 0.8],
['melon', 3.5],
]);
const caros = Iterator.from(precios.entries())
.filter(([, precio]) => precio > 1)
.map(([nombre]) => nombre)
.toArray();
console.log(caros); // ['manzana', 'melon']
Encadenar helpers: lazy pipeline
Uno de los casos más útiles es construir pipelines sobre fuentes de datos grandes. Supón que tienes una fuente que produce miles de registros y quieres procesar solo los cien primeros válidos:
const procesados = Iterator.from(fuenteGrande)
.filter(registro => esValido(registro))
.map(registro => transformar(registro))
.take(100)
.toArray();
El .take(100) al final hace que todo el pipeline se detenga en cuanto tiene cien elementos válidos. No importa que la fuente tenga diez mil registros: si los primeros 200 ya producen cien válidos, el resto no se toca.
Sin .take(), el pipeline procesaría la fuente entera. Con take bien colocado, el ahorro puede ser enorme dependiendo de los datos.
Diferencia con los métodos de Array
Los arrays llevan .filter() y .map() desde hace mucho tiempo, pero son eager: cada llamada recorre el array completo y crea uno nuevo. Si encadenas tres métodos, creas tres arrays.
Con los iterator helpers el comportamiento es distinto. Cada método devuelve un iterador que envuelve al anterior. Nada se ejecuta hasta que consumes el resultado. Y si hay un .take(n) en el pipeline, todo lo anterior procesa exactamente los elementos necesarios para producir esos N resultados, y no más.
Para arrays pequeños la diferencia es irrelevante. Con datasets grandes, la diferencia en memoria y tiempo puede ser significativa.
Cómo usarlos hoy
Si tu proyecto apunta a navegadores modernos o a Node.js 22+, puedes usar los iterator helpers directamente, sin instalar nada. La compatibilidad ya es amplia:
- Chrome 122+
- Firefox 131+
- Safari 18+
- Node.js 22+
Si necesitas soporte para versiones más antiguas, core-js incluye un polyfill completo de los iterator helpers. Solo tienes que añadirlo a tu configuración de Babel o importarlo directamente:
import 'core-js/proposals/iterator-helpers';
A partir de ahí, el código es idéntico. La API no cambia.
Para más contexto sobre el lenguaje base donde se integra esta propuesta, puedes leer sobre JavaScript como lenguaje base. Y si combinas iterator helpers con tipado estático, TypeScript y las novedades del lenguaje cubre cómo el sistema de tipos gestiona estos nuevos iteradores en 2025.
Imagen: Pexels / Markus Spiske
